"""
缓存服务模块
"""

import json
import pickle
import asyncio
from typing import Any, Optional, Dict, List, Union
from datetime import datetime, timedelta
from abc import ABC, abstractmethod
import logging
import hashlib
import zlib

try:
    import redis.asyncio as redis
    REDIS_AVAILABLE = True
except ImportError:
    REDIS_AVAILABLE = False
    redis = None

from ..core.config import settings
from ..core.exceptions import CacheError
from ..core.logging import get_logger

logger = get_logger(__name__)


class CacheBackend(ABC):
    """缓存后端抽象类"""
    
    @abstractmethod
    async def get(self, key: str) -> Optional[Any]:
        """获取缓存值"""
        pass
    
    @abstractmethod
    async def set(
        self, 
        key: str, 
        value: Any, 
        expire: Optional[int] = None
    ) -> bool:
        """设置缓存值"""
        pass
    
    @abstractmethod
    async def delete(self, key: str) -> bool:
        """删除缓存"""
        pass
    
    @abstractmethod
    async def delete_pattern(self, pattern: str) -> int:
        """根据模式删除缓存"""
        pass
    
    @abstractmethod
    async def exists(self, key: str) -> bool:
        """检查缓存是否存在"""
        pass
    
    @abstractmethod
    async def expire(self, key: str, seconds: int) -> bool:
        """设置缓存过期时间"""
        pass
    
    @abstractmethod
    async def ttl(self, key: str) -> int:
        """获取缓存剩余时间"""
        pass
    
    @abstractmethod
    async def flush_all(self) -> bool:
        """清空所有缓存"""
        pass


class MemoryCache(CacheBackend):
    """内存缓存后端"""
    
    def __init__(self):
        self._cache: Dict[str, Dict[str, Any]] = {}
        self._lock = asyncio.Lock()
    
    async def get(self, key: str) -> Optional[Any]:
        async with self._lock:
            if key not in self._cache:
                return None
            
            item = self._cache[key]
            
            # 检查是否过期
            if item['expire_at'] and datetime.utcnow() > item['expire_at']:
                del self._cache[key]
                return None
            
            return item['value']
    
    async def set(
        self, 
        key: str, 
        value: Any, 
        expire: Optional[int] = None
    ) -> bool:
        async with self._lock:
            expire_at = None
            if expire:
                expire_at = datetime.utcnow() + timedelta(seconds=expire)
            
            self._cache[key] = {
                'value': value,
                'expire_at': expire_at,
                'created_at': datetime.utcnow()
            }
            
            return True
    
    async def delete(self, key: str) -> bool:
        async with self._lock:
            if key in self._cache:
                del self._cache[key]
                return True
            return False
    
    async def delete_pattern(self, pattern: str) -> int:
        import fnmatch
        
        async with self._lock:
            keys_to_delete = []
            for key in self._cache:
                if fnmatch.fnmatch(key, pattern):
                    keys_to_delete.append(key)
            
            for key in keys_to_delete:
                del self._cache[key]
            
            return len(keys_to_delete)
    
    async def exists(self, key: str) -> bool:
        value = await self.get(key)
        return value is not None
    
    async def expire(self, key: str, seconds: int) -> bool:
        async with self._lock:
            if key not in self._cache:
                return False
            
            expire_at = datetime.utcnow() + timedelta(seconds=seconds)
            self._cache[key]['expire_at'] = expire_at
            
            return True
    
    async def ttl(self, key: str) -> int:
        async with self._lock:
            if key not in self._cache:
                return -1
            
            item = self._cache[key]
            if not item['expire_at']:
                return -1
            
            ttl = (item['expire_at'] - datetime.utcnow()).total_seconds()
            return int(ttl) if ttl > 0 else -2
    
    async def flush_all(self) -> bool:
        async with self._lock:
            self._cache.clear()
            return True


class RedisCache(CacheBackend):
    """Redis缓存后端"""
    
    def __init__(self, redis_url: str):
        if not REDIS_AVAILABLE:
            raise CacheError("Redis is not available. Install redis package.")
        
        self.redis_url = redis_url
        self._client = None
        self._connected = False
    
    async def _get_client(self):
        """获取Redis客户端"""
        if not self._client:
            try:
                self._client = redis.from_url(self.redis_url, decode_responses=False)
                await self._client.ping()
                self._connected = True
                logger.info("Redis cache connected")
            except Exception as e:
                logger.error(f"Failed to connect to Redis: {e}")
                raise CacheError(f"Redis connection failed: {str(e)}")
        
        return self._client
    
    async def get(self, key: str) -> Optional[Any]:
        try:
            client = await self._get_client()
            value = await client.get(key)
            
            if value is None:
                return None
            
            # 反序列化
            return pickle.loads(zlib.decompress(value))
            
        except Exception as e:
            logger.error(f"Redis get error: {e}")
            return None
    
    async def set(
        self, 
        key: str, 
        value: Any, 
        expire: Optional[int] = None
    ) -> bool:
        try:
            client = await self._get_client()
            
            # 序列化
            serialized_value = zlib.compress(pickle.dumps(value))
            
            if expire:
                return await client.setex(key, expire, serialized_value)
            else:
                return await client.set(key, serialized_value)
                
        except Exception as e:
            logger.error(f"Redis set error: {e}")
            return False
    
    async def delete(self, key: str) -> bool:
        try:
            client = await self._get_client()
            return bool(await client.delete(key))
        except Exception as e:
            logger.error(f"Redis delete error: {e}")
            return False
    
    async def delete_pattern(self, pattern: str) -> int:
        try:
            client = await self._get_client()
            keys = await client.keys(pattern)
            if keys:
                return await client.delete(*keys)
            return 0
        except Exception as e:
            logger.error(f"Redis delete_pattern error: {e}")
            return 0
    
    async def exists(self, key: str) -> bool:
        try:
            client = await self._get_client()
            return bool(await client.exists(key))
        except Exception as e:
            logger.error(f"Redis exists error: {e}")
            return False
    
    async def expire(self, key: str, seconds: int) -> bool:
        try:
            client = await self._get_client()
            return bool(await client.expire(key, seconds))
        except Exception as e:
            logger.error(f"Redis expire error: {e}")
            return False
    
    async def ttl(self, key: str) -> int:
        try:
            client = await self._get_client()
            return await client.ttl(key)
        except Exception as e:
            logger.error(f"Redis ttl error: {e}")
            return -1
    
    async def flush_all(self) -> bool:
        try:
            client = await self._get_client()
            return bool(await client.flushdb())
        except Exception as e:
            logger.error(f"Redis flush_all error: {e}")
            return False
    
    async def close(self):
        """关闭Redis连接"""
        if self._client:
            await self._client.close()
            self._connected = False


class CacheService:
    """缓存服务"""
    
    def __init__(self, backend: Optional[CacheBackend] = None):
        self.backend = backend
        self._stats = {
            'hits': 0,
            'misses': 0,
            'sets': 0,
            'deletes': 0
        }
    
    async def _get_backend(self) -> CacheBackend:
        """获取缓存后端"""
        if not self.backend:
            if settings.REDIS_URL and REDIS_AVAILABLE:
                try:
                    self.backend = RedisCache(settings.REDIS_URL)
                    logger.info("Using Redis cache backend")
                except Exception as e:
                    logger.warning(f"Redis not available, falling back to memory cache: {e}")
                    self.backend = MemoryCache()
            else:
                self.backend = MemoryCache()
                logger.info("Using memory cache backend")
        
        return self.backend
    
    def _generate_key(self, prefix: str, identifier: str) -> str:
        """生成缓存键"""
        return f"{prefix}:{identifier}"
    
    def _generate_complex_key(self, prefix: str, *args, **kwargs) -> str:
        """生成复合缓存键"""
        key_parts = [prefix]
        
        # 添加位置参数
        for arg in args:
            key_parts.append(str(arg))
        
        # 添加关键字参数（排序确保一致性）
        for k in sorted(kwargs.keys()):
            key_parts.append(f"{k}:{kwargs[k]}")
        
        key = ":".join(key_parts)
        
        # 如果键太长，使用哈希
        if len(key) > 200:
            key_hash = hashlib.md5(key.encode()).hexdigest()
            return f"{prefix}:hash:{key_hash}"
        
        return key
    
    async def get(self, key: str) -> Optional[Any]:
        """获取缓存值"""
        try:
            backend = await self._get_backend()
            value = await backend.get(key)
            
            if value is not None:
                self._stats['hits'] += 1
                return value
            else:
                self._stats['misses'] += 1
                return None
                
        except Exception as e:
            logger.error(f"Cache get error: {e}")
            self._stats['misses'] += 1
            return None
    
    async def set(
        self, 
        key: str, 
        value: Any, 
        expire: Optional[int] = None
    ) -> bool:
        """设置缓存值"""
        try:
            backend = await self._get_backend()
            result = await backend.set(key, value, expire)
            
            if result:
                self._stats['sets'] += 1
            
            return result
            
        except Exception as e:
            logger.error(f"Cache set error: {e}")
            return False
    
    async def delete(self, key: str) -> bool:
        """删除缓存"""
        try:
            backend = await self._get_backend()
            result = await backend.delete(key)
            
            if result:
                self._stats['deletes'] += 1
            
            return result
            
        except Exception as e:
            logger.error(f"Cache delete error: {e}")
            return False
    
    async def delete_pattern(self, pattern: str) -> int:
        """根据模式删除缓存"""
        try:
            backend = await self._get_backend()
            result = await backend.delete_pattern(pattern)
            self._stats['deletes'] += result
            return result
        except Exception as e:
            logger.error(f"Cache delete_pattern error: {e}")
            return 0
    
    async def exists(self, key: str) -> bool:
        """检查缓存是否存在"""
        try:
            backend = await self._get_backend()
            return await backend.exists(key)
        except Exception as e:
            logger.error(f"Cache exists error: {e}")
            return False
    
    async def get_or_set(
        self, 
        key: str, 
        func, 
        expire: Optional[int] = None
    ) -> Any:
        """获取缓存值，如果不存在则设置"""
        value = await self.get(key)
        
        if value is not None:
            return value
        
        # 生成新值
        if asyncio.iscoroutinefunction(func):
            value = await func()
        else:
            value = func()
        
        # 设置缓存
        await self.set(key, value, expire)
        
        return value
    
    async def get_typed(self, key: str, expected_type: type) -> Optional[Any]:
        """获取指定类型的缓存值"""
        value = await self.get(key)
        
        if value is not None and not isinstance(value, expected_type):
            logger.warning(f"Cache type mismatch: expected {expected_type}, got {type(value)}")
            await self.delete(key)
            return None
        
        return value
    
    async def increment(self, key: str, amount: int = 1) -> int:
        """递增缓存值"""
        try:
            backend = await self._get_backend()
            
            # 获取当前值
            current = await self.get(key)
            if current is None:
                current = 0
            elif not isinstance(current, int):
                raise CacheError(f"Cannot increment non-integer value: {type(current)}")
            
            # 递增并保存
            new_value = current + amount
            await self.set(key, new_value)
            
            return new_value
            
        except Exception as e:
            logger.error(f"Cache increment error: {e}")
            raise CacheError(f"Increment operation failed: {str(e)}")
    
    def get_stats(self) -> Dict[str, int]:
        """获取缓存统计"""
        total = self._stats['hits'] + self._stats['misses']
        hit_rate = self._stats['hits'] / total if total > 0 else 0
        
        return {
            **self._stats,
            'total': total,
            'hit_rate': round(hit_rate, 4)
        }
    
    def reset_stats(self):
        """重置统计"""
        self._stats = {
            'hits': 0,
            'misses': 0,
            'sets': 0,
            'deletes': 0
        }
    
    async def health_check(self) -> Dict[str, Any]:
        """缓存健康检查"""
        try:
            backend = await self._get_backend()
            
            # 测试设置和获取
            test_key = "health_check"
            test_value = {"timestamp": datetime.utcnow().isoformat()}
            
            set_result = await backend.set(test_key, test_value, expire=10)
            get_result = await backend.get(test_key)
            
            # 清理测试数据
            await backend.delete(test_key)
            
            backend_type = type(backend).__name__
            
            return {
                "status": "healthy",
                "backend": backend_type,
                "test_passed": set_result and get_result == test_value,
                "stats": self.get_stats()
            }
            
        except Exception as e:
            return {
                "status": "unhealthy",
                "error": str(e),
                "backend": type(self.backend).__name__ if self.backend else None,
                "stats": self.get_stats()
            }
    
    async def cleanup(self):
        """清理缓存资源"""
        if self.backend and hasattr(self.backend, 'close'):
            await self.backend.close()
            logger.info("Cache backend cleaned up")


# 全局缓存服务实例
cache_service = CacheService()


# 缓存装饰器
def cache(key_prefix: str, expire: Optional[int] = None):
    """缓存装饰器"""
    def decorator(func):
        async def wrapper(*args, **kwargs):
            # 生成缓存键
            cache_key = cache_service._generate_complex_key(
                key_prefix, 
                *args, 
                **kwargs
            )
            
            # 尝试从缓存获取
            result = await cache_service.get(cache_key)
            
            if result is not None:
                return result
            
            # 执行函数
            if asyncio.iscoroutinefunction(func):
                result = await func(*args, **kwargs)
            else:
                result = func(*args, **kwargs)
            
            # 设置缓存
            await cache_service.set(cache_key, result, expire)
            
            return result
        
        return wrapper
    return decorator


def cache_by_id(key_prefix: str, id_param: str = 'id', expire: Optional[int] = None):
    """根据ID缓存装饰器"""
    def decorator(func):
        async def wrapper(*args, **kwargs):
            # 获取ID参数
            if id_param in kwargs:
                item_id = kwargs[id_param]
            elif len(args) > 0:
                item_id = args[0]
            else:
                raise ValueError(f"ID parameter '{id_param}' not found")
            
            # 生成缓存键
            cache_key = cache_service._generate_key(key_prefix, str(item_id))
            
            # 尝试从缓存获取
            result = await cache_service.get(cache_key)
            
            if result is not None:
                return result
            
            # 执行函数
            if asyncio.iscoroutinefunction(func):
                result = await func(*args, **kwargs)
            else:
                result = func(*args, **kwargs)
            
            # 设置缓存
            await cache_service.set(cache_key, result, expire)
            
            return result
        
        return wrapper
    return decorator


# 缓存上下文管理器
class CacheContext:
    """缓存上下文管理器"""
    
    def __init__(self, prefix: str):
        self.prefix = prefix
        self.keys_to_invalidate: List[str] = []
    
    def add_key(self, key: str):
        """添加需要失效的键"""
        self.keys_to_invalidate.append(key)
    
    def generate_key(self, identifier: str) -> str:
        """生成缓存键"""
        return cache_service._generate_key(self.prefix, identifier)
    
    async def invalidate_all(self):
        """使所有相关缓存失效"""
        pattern = f"{self.prefix}:*"
        await cache_service.delete_pattern(pattern)
    
    async def invalidate_keys(self):
        """使指定键失效"""
        for key in self.keys_to_invalidate:
            await cache_service.delete(key)
    
    async def __aenter__(self):
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        # 上下文退出时自动使缓存失效
        await self.invalidate_keys()


# 缓存预热服务
class CacheWarmer:
    """缓存预热服务"""
    
    def __init__(self):
        self.warmup_tasks: List[Dict[str, Any]] = []
    
    def add_task(self, func, key_prefix: str, expire: Optional[int] = None):
        """添加预热任务"""
        self.warmup_tasks.append({
            'func': func,
            'key_prefix': key_prefix,
            'expire': expire
        })
    
    async def warmup_all(self):
        """执行所有预热任务"""
        logger.info(f"Starting cache warmup with {len(self.warmup_tasks)} tasks")
        
        for task in self.warmup_tasks:
            try:
                # 生成缓存键
                cache_key = cache_service._generate_key(task['key_prefix'], 'warmup')
                
                # 检查是否已存在
                if await cache_service.exists(cache_key):
                    logger.info(f"Cache key {cache_key} already exists, skipping")
                    continue
                
                # 执行预热函数
                if asyncio.iscoroutinefunction(task['func']):
                    result = await task['func']()
                else:
                    result = task['func']()
                
                # 设置缓存
                await cache_service.set(cache_key, result, task['expire'])
                
                logger.info(f"Cache warmup completed for {task['key_prefix']}")
                
            except Exception as e:
                logger.error(f"Cache warmup failed for {task['key_prefix']}: {e}")


# 全局缓存预热器
cache_warmer = CacheWarmer()
